package com.ydlclass.loadbnlancer.impl;

import com.ydlclass.YrpcBootstrap;
import com.ydlclass.exceptions.LoadBalancerException;
import com.ydlclass.loadbnlancer.AbstractLoadBalance;
import com.ydlclass.loadbnlancer.Selector;
import com.ydlclass.transport.message.YrpcRequest;
import lombok.extern.slf4j.Slf4j;

import java.net.InetSocketAddress;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicInteger;
/** loadBalance策略
 * @Author duWenJian
 * @Date 2023/11/4 13:47
 * @Version 1.0
 */
@Slf4j
public class ConsistentHashBalancer extends AbstractLoadBalance {

    @Override
    protected Selector getSelector(List<InetSocketAddress> serviceList) {
        return new ConsistentHashSelector(serviceList,128);
    }

    /**
     * 一致性hash算法的具体实现
     */
    private static class ConsistentHashSelector implements Selector{
        //hash环用来存储服务器节点
        private SortedMap<Integer, InetSocketAddress> circle = new TreeMap<>();
        //虚拟节点的个数
        private int virtualNodes;

        public ConsistentHashSelector(List<InetSocketAddress> serviceList, int virtualNodes) {
            // 将节点转化为虚拟节点，进行挂载
            this.virtualNodes = virtualNodes;
            for (InetSocketAddress address : serviceList) {
                // 需要把每个节点加入到hash环上
                addNodeToCircle(address);
            }
        }

        @Override
        public InetSocketAddress getNext() {
            // 1、hash环已经建立好了，接下来要对请求的要素进行处理，
            // 有没有办法获取具体的请求内容:yrpcRequest   -->ThreadLocal
            YrpcRequest request = YrpcBootstrap.THREAD_LOCAL.get();
            // 2、我们想根据请求的一些特征来选择服务器
            String requestId = Long.toString(request.getRequestId());
            // 3、请求id做hash，字符串默认的hash不太友好
            int hash = hash(requestId);
            // 判断该hash是否坐落在hash环上，如果不在，就近选择一个比较近的
            if (!circle.containsKey(hash)){
                SortedMap<Integer, InetSocketAddress> sortedMap = circle.tailMap(hash);
                hash = sortedMap.isEmpty() ? circle.firstKey() : sortedMap.firstKey();
            }
            return circle.get(hash);
        }

        /**
         * 将每个节点挂载到hash环上
         * @param address 节点地址
         */
        private void addNodeToCircle(InetSocketAddress address) {
            // 将每个节点生成对应的虚拟节点进行挂载
            for (int i = 0; i < virtualNodes; i++) {
                int hash = hash(address.toString()+"-"+i);
                // 挂载到hash环上
                circle.put(hash,address);
                if (log.isDebugEnabled()){
                    log.debug("hash为{}的节点已经挂载到了哈希环上",hash);
                }
            }
        }

        /**
         * 将每个节点挂载到hash环上
         * @param address 节点地址
         */
        private void removeNodeFromCircle(InetSocketAddress address) {
            // 将每个节点生成对应的虚拟节点进行挂载
            for (int i = 0; i < virtualNodes; i++) {
                int hash = hash(address.toString()+"-"+i);
                // 挂载到hash环上
                circle.remove(hash);
            }
        }


        /**
         * 具体的hash算法  todo 无法散列的生成
         * @param s
         * @return
         */
//        private int hash(String s) {
//            MessageDigest md;
//            try {
//                md = MessageDigest.getInstance("MD5");
//            } catch (NoSuchAlgorithmException e) {
//                throw new RuntimeException(e);
//            }
//            byte[] bytes = md.digest(s.getBytes());
//            // md5得到一个字节数组，但是我们想要得到一个 int 4个字符
//            int res = 0;
//            for (int i = 0; i < 4; i++) {
//
//                res = res << 8;
//                if (bytes[i] < 0){
//                    res = res | (bytes[i] & 255);
//                } else {
//                    res = res | bytes[i];
//                }
//
//            }
//            return res;
//        }
        private int hash(String s){
            MessageDigest md;
            try {
                md = MessageDigest.getInstance("SHA-256");
            } catch (NoSuchAlgorithmException e) {
                throw new RuntimeException(e);
            }
            byte[] bytes = md.digest(s.getBytes());

            int res = 0;
            for (int i = 0; i < 4; i++) {
                res = res << 8;
                res = res | (bytes[i] & 0xFF);
            }

            int numBuckets = 1000; // 哈希桶数量
            return Math.floorMod(res, numBuckets);
        }
    }
}
